#include <core/common/global.h>
#include <core/common/string.h>
#include <core/common/file.h>

#ifndef OS_PLATFORM_WINDOWS
	#include <dlfcn.h>
#else
	#include <shellapi.h>
#endif

//
// helpers
//

#ifdef OS_PLATFORM_WINDOWS

struct timezone
{
	int tz_minuteswest;
	int tz_dsttime;
};

#define EPOCHFILETIME 116444736000000000i64

inline int gettimeofday( struct timeval *tv, struct timezone *tz )
{
	FILETIME        ft;
	LARGE_INTEGER   li;
	static int      tzflag = 0;

	if (tv)
	{
		GetSystemTimeAsFileTime(&ft);
		li.LowPart  = ft.dwLowDateTime;
		li.HighPart = ft.dwHighDateTime;
		//t  = li.QuadPart;       /* In 100-nanosecond intervals */
		li.QuadPart -= EPOCHFILETIME;     /* Offset to the Epoch time */
		li.QuadPart /= 10;                /* In microseconds */
		tv->tv_sec  = (long)(li.QuadPart / 1000000);
		tv->tv_usec = (long)(li.QuadPart % 1000000);
	}

	if (tz)
	{
		if (!tzflag)
		{
			_tzset();
			tzflag ++;
		}
		tz->tz_minuteswest = _timezone / 60;
		tz->tz_dsttime = _daylight;
	}

	return 0;
}
#endif //OS_PLATFORM_WINDOWS

int64 core::common::getMicroTime()
{
	struct timeval nTime;
	gettimeofday(&nTime, NULL);

	return (int64)(nTime.tv_sec) * core::common::kMicroUnitsPerSec + (int64)(nTime.tv_usec);
}

int64 core::common::getHighResolutionTime()
{
	static int64 upRefererTime = 0;
	int64 upTime = 0;

#ifdef OS_PLATFORM_WINDOWS
	LARGE_INTEGER freq = {0},ticks = {0};
	QueryPerformanceFrequency(&freq);
	QueryPerformanceCounter(&ticks);
	//int64 upTime = freq.QuadPart > 0 ? ticks.QuadPart * common::kMicroUnitsPerSec / freq.QuadPart : 0;
	upTime = freq.QuadPart > 0 ? (int64)(double(ticks.QuadPart) / double(freq.QuadPart) * double(core::common::kMicroUnitsPerSec)) : 0;
#elif defined(ARM_LINUX)
	struct timeval timeValue;
	gettimeofday(&timeValue, NULL);
	return (int64)(timeValue.tv_sec) * core::common::kMicroUnitsPerSec + (int64)(timeValue.tv_usec); 
#else
	struct timespec timeValue = {0, 0};
	clock_gettime(CLOCK_MONOTONIC, &timeValue);
	upTime = (int64)(timeValue.tv_sec) * core::common::kMicroUnitsPerSec + (int64)(timeValue.tv_nsec) / core::common::kNanoUnitsPerMicro; 
#endif //WIN32

	if( upRefererTime == 0 )
	{
		upRefererTime = core::common::getMicroTime() - upTime;
	}
	return upTime + upRefererTime;
}

int64 core::common::erfTime2MicroTime( uint64 erfTime, bool unixTime )
{
	uint64 tsErf = erfTime;
	uint64 seconds = (tsErf >> 32) & 0xffffffffLL;

	tsErf = ((tsErf & 0xffffffffLL) * core::common::kMicroUnitsPerSec);
	tsErf += (tsErf & 0x80000000LL) << 1;
	uint32 microSeconds = (uint32)(tsErf >> 32);

	if (microSeconds >= core::common::kMicroUnitsPerSec) 
	{
		microSeconds -= core::common::kMicroUnitsPerSec;
		seconds  += 1;
	}

	if ( !unixTime ) seconds -= 2208988800LL;// 1900-1970 seconds

	return int64(seconds) * core::common::kMicroUnitsPerSec + int64(microSeconds);
}

uint64 core::common::microTime2ErfTime( int64 microTime, bool unixTime )
{
	uint64 seconds = (uint64)(microTime / core::common::kMicroUnitsPerSec);
	uint64 microSeconds = (uint64)(microTime % core::common::kMicroUnitsPerSec);
	if ( !unixTime ) seconds += 2208988800LL; // 1900-1970 seconds

	return (seconds << 32) | ((microSeconds << 32) / core::common::kMicroUnitsPerSec);
}

time_t core::common::getLocalTimezoneSeconds()
{
#ifdef OS_PLATFORM_WINDOWS
	TIME_ZONE_INFORMATION zoneInfo;
	GetTimeZoneInformation(&zoneInfo);

	return (time_t)(zoneInfo.Bias * 60);
#else
	struct tm gmtTime;
	time_t nowTimestamp = time(NULL);
	core::common::getGmtTimestamp(nowTimestamp, gmtTime);
	time_t nGmtTimestamp = mktime(&gmtTime);

	return (time_t)(nGmtTimestamp - nowTimestamp);
#endif
}

bool core::common::getLocalTimestamp( time_t timestamp, struct tm &timeValue )
{
#ifdef OS_PLATFORM_WINDOWS
	return localtime_s(&timeValue, &timestamp) == 0;
#else
	return localtime_r(&timestamp, &timeValue) != NULL;
#endif
}

bool core::common::getGmtTimestamp( time_t timestamp, struct tm &timeValue )
{
#ifdef OS_PLATFORM_WINDOWS
	return gmtime_s(&timeValue, &timestamp) == 0;
#else
	return gmtime_r(&timestamp, &timeValue) != NULL;
#endif
}

void core::common::milliSleep( int millSecs )
{
#ifdef OS_PLATFORM_WINDOWS
	::Sleep(millSecs);
#else
	::usleep(((__useconds_t)millSecs) * 1000);
#endif // OS_PLATFORM_WINDOWS
}

void core::common::microSleep( int microSecs )
{
#ifdef OS_PLATFORM_WINDOWS
	::Sleep(microSecs / 1000);
#else
	::usleep((__useconds_t)microSecs);
#endif // OS_PLATFORM_WINDOWS
}

std::string core::common::getWorkingDirectory()
{
	std::string directory;

	directory.resize(10240);
#ifdef OS_PLATFORM_WINDOWS
	DWORD dwSize = GetCurrentDirectoryA(directory.size() - 1, (char *)directory.c_str());
	if( dwSize > 0 ) directory.resize(dwSize);
	else directory.clear();
#else
	if( getcwd((char *)directory.c_str(), directory.size() - 1) != NULL )
	{
		size_t nLength = strlen(directory.c_str());
		directory.resize(nLength);
	}
	else
	{
		directory.clear();
	}
#endif

	return directory;
}

bool core::common::setWorkingDirectory( const std::string &directory )
{
	std::string normalDirectory = core::common::File::normalizePathTo(directory);

#ifdef OS_PLATFORM_WINDOWS
	return SetCurrentDirectoryA(normalDirectory.c_str()) == TRUE;
#else
	return chdir(normalDirectory.c_str()) == 0;
#endif //WIN32
}

size_t core::common::getProcessId()
{
#ifdef OS_PLATFORM_WINDOWS
	return (size_t)::GetCurrentProcessId();
#else
	return (size_t)getpid();
#endif //WIN32
}

size_t core::common::getThreadId()
{
#ifdef OS_PLATFORM_WINDOWS
	return ::GetCurrentThreadId();
#else
	return (size_t)pthread_self();
#endif //WIN32
}

uint32 core::common::getErrorCode()
{
#ifdef OS_PLATFORM_WINDOWS
	return (uint32)GetLastError();
#else
	return (uint32)errno;
#endif //WIN32
}

void core::common::setErrorCode( uint32 code )
{
#ifdef OS_PLATFORM_WINDOWS
	SetLastError(code);
#else
	errno = code;
#endif //WIN32
}

std::string core::common::getErrorDescription( uint32 code /* = -1 */ )
{
	uint32 realCode = (((uint32)-1) == code) ? getErrorCode() : code;
#ifdef OS_PLATFORM_WINDOWS
	LPVOID messageBuffer = NULL;
	if (!FormatMessageA( 
		FORMAT_MESSAGE_ALLOCATE_BUFFER | 
		FORMAT_MESSAGE_FROM_SYSTEM | 
		FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		realCode,
		MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), // default language
		(LPSTR) &messageBuffer,
		0,
		NULL ))
	{
		// Handle the error.
		return std::string("");
	}

	// trim end blank characters
	char *messageData = (char *)messageBuffer;
	int messageLength = (int)strlen(messageData);
	while( messageLength > 0 && isspace(messageData[messageLength - 1]) )	
	{
		messageData[messageLength - 1] = 0;
		messageLength --;
	}

	std::string strError((const char *)messageBuffer);
	LocalFree(messageBuffer);

	return strError;

#else
	return std::string(strerror(realCode));
#endif
}

uint32 core::common::getNetworkErrorCode()
{
#ifdef OS_PLATFORM_WINDOWS
	return (uint32)WSAGetLastError();
#else
	return (uint32)h_errno;
#endif //WIN32
}

void core::common::setNetworkErrorCode( uint32 code )
{
#ifdef OS_PLATFORM_WINDOWS
	WSASetLastError(code);
#else
	h_errno = code;
#endif //WIN32
}

std::string core::common::getNetworkErrorDescription( uint32 code /* = -1 */ )
{
	uint32 eealCode = (((uint32)-1) == code) ? getNetworkErrorCode() : code;
#ifdef OS_PLATFORM_WINDOWS
	return getErrorDescription(eealCode);
#else
	return std::string(hstrerror(eealCode));
#endif
}

void *core::common::loadDynamicLibrary( const std::string &file, int mode )
{
#ifdef OS_PLATFORM_WINDOWS
	return LoadLibraryA(file.c_str());
#else
	return dlopen(file.c_str(), mode < 0 ? RTLD_LAZY : mode);
#endif
}

bool core::common::freeDynamicLibrary( void *libModule )
{
#ifdef OS_PLATFORM_WINDOWS
	return FreeLibrary((HMODULE)libModule) ? true : false;
#else
	return dlclose(libModule) == 0;
#endif
}

void *core::common::getDynamicSymbol( void *libModule, const std::string &name )
{
#ifdef OS_PLATFORM_WINDOWS
	return GetProcAddress((HMODULE)libModule, name.c_str());
#else
	return dlsym(libModule, name.c_str());
#endif
}

std::string core::common::getDynamicLibraryError()
{
#ifdef OS_PLATFORM_WINDOWS
	return core::common::getErrorDescription(core::common::getErrorCode());
#else
	return std::string(dlerror());
#endif
}

void core::common::exit( int code )
{
	// more extension
	::exit(code);
}

bool core::common::getEnvironmentVariable( const std::string &name, std::string &value )
{
#ifdef OS_PLATFORM_WINDOWS
	size_t requiredSize;
	if ( 0 != getenv_s(&requiredSize, NULL, 0, name.c_str())) //Var not found
	{
		return false;
	}

	value.resize(requiredSize);
	if ( 0 != getenv_s(&requiredSize, (char *)value.c_str(), requiredSize, name.c_str())) // variable not found
	{
		return false;
	}
#else
	char *buffer = getenv(name.c_str());
	if ( NULL == buffer)
	{
		return false;
	}
	value = buffer;
#endif

	return true;
}

bool core::common::getEnvironmentVariable( const std::string &name, std::vector<std::string> &values )
{
	std::string strValue;
	if ( !getEnvironmentVariable(name, strValue) )
	{
		return false;
	}

#ifdef OS_PLATFORM_WINDOWS
	const char *kEnvSeparator = ";";
#else
	const char *kEnvSeparator = ":";
#endif

	core::common::String::split(strValue, values, kEnvSeparator);
	return true;
}

uint32 core::common::reverseEndian( const uint32 number )
{
	return ((number & 0xFF) << 24) | ((number & 0xFF00) << 8) |
		((number & 0xFF0000) >> 8) | ((number & 0xFF000000) >> 24);
}

// network order to host order
uint16 core::common::network2Host16( uint16 number )
{
	return ntohs(number);
}

uint32 core::common::network2Host32( uint32 number )
{
	return ntohl(number);
}

uint64 core::common::network2Host64( uint64 number )
{
	if( ntohl(1) == 1 ) 
	{
		return number;
	}

	return ((uint64)ntohl((uint32)(number >> 32))) + (((uint64)ntohl((uint32)number)) << 32);
}

// host order to network order
uint16 core::common::host2Network16( uint16 number )
{
	return htons(number);
}

uint32 core::common::host2Network32( uint32 number )
{
	return htonl(number);
}

uint64 core::common::host2Network64( uint64 number )
{
	if( htonl(1) == 1 ) 
	{
		return number;
	}

	return ((uint64)htonl((uint32)(number >> 32))) + (((uint64)htonl((uint32)number)) << 32);
}

#if defined(OS_PLATFORM_WINDOWS) && defined(CORE_COMMON_EXPORT__)
BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved )
{
	switch( dwReason )
	{
	case DLL_PROCESS_ATTACH:
	case DLL_PROCESS_DETACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
		break;
	}

	return TRUE;
}
#endif

